// -*- coding: utf-8 -*-
/****************************************************************************
 * Elisa 1.0                                                                *
 *--------------------------------------------------------------------------*
 * Author: Laurent Granvilliers, Vincent Sorin                              *
 * Copyright (c) 2004 Laboratoire d'Informatique de Nantes Atlantique       *
 *--------------------------------------------------------------------------*
 * Elisa is distributed WITHOUT ANY WARRANTY. Read the associated           *
 * COPYRIGHT file for more details.                                         *
 ****************************************************************************/

#ifndef _ELISA_INTERVAL_GAOL_H
#define _ELISA_INTERVAL_GAOL_H

#include <iostream>
#include <string>
#include <cmath>
#include <gaol/gaol_interval.h>

#include "elisa_interval2.h"

NAMESPACE_ELISA

typedef double Bound;
typedef gaol::interval Icode;
typedef BasicInterval<Icode, double> Interval;

/**
 * @ingroup Interval
 */
template <>
struct IntervalTraits2<Icode, double>
{
	/**
	 * @name Constants
	 */
	//@{
	static const Icode universe()
	{
		return Icode::universe;
	}
	static const Icode zero()
	{
		return Icode::zero;
	}
	static const Icode one()
	{
		return Icode::one;
	}
	static const Icode emptySet()
	{
		return Icode::emptyset;
	}
	static const Icode pi()
	{
		return Icode::pi;
	}
	//@}

	/// @brief Copy
	static Icode&
	copy(Icode& lhs, const Icode& rhs)
	{
		return lhs = rhs;
	}

	/**
	 * @name Arithmetic operations
	 */
	//@{
	static const Icode 
	plus(const Icode& lhs, const Icode& rhs)
	{
		return gaol::operator+(lhs, rhs);
	}
	static const Icode 
	minus(const Icode& lhs, const Icode& rhs)
	{
		return gaol::operator-(lhs, rhs);
	}
	static const Icode 
	multiply(const Icode& lhs, const Icode& rhs)
	{
		return gaol::operator*(lhs, rhs);
	}
	static const Icode 
	divide(const Icode& lhs, const Icode& rhs)
	{
		return gaol::operator/(lhs, rhs);
	}

	static const Icode 
	plus(const Icode& x)
	{
		return Icode(x);
	}
	static const Icode 
	minus(const Icode& x)
	{
		return x.operator-();
	}


	static const Icode 
	min(const Icode& lhs, const Icode& rhs)
	{
		return gaol::min(lhs, rhs);
	}
	static const Icode 
	max(const Icode& lhs, const Icode& rhs)
	{
		return gaol::max(lhs, rhs);
	}


	static const Icode 
	abs(const Icode& x)
	{
		return gaol::abs(x);
	}

	static const Icode 
	sqr(const Icode& x)
	{
		return gaol::sqr(x);
	}

	static const Icode 
	sqrt(const Icode& x)
	{
		return gaol::sqrt(x);
	}

	static const Icode 
	exp(const Icode& x)
	{
		return gaol::exp(x);
	}

	static const Icode 
	log(const Icode& x)
	{
		return gaol::log(x);
	}

	static const Icode 
	pow(const Icode& x, int n)
	{
		return gaol::pow(x, n);
	}

	static const Icode 
	root(const Icode& x, int n)
	{
		return gaol::nth_root(x, n);
	}


	static const Icode 
	sin(const Icode& x)
	{
		return gaol::sin(x);
	}
	static const Icode 
	cos(const Icode& x)
	{
		return gaol::cos(x);
	}
	static const Icode 
	tan(const Icode& x)
	{
		return gaol::tan(x);
	}
	static const Icode 
	sinh(const Icode& x)
	{
		return gaol::sinh(x);
	}
	static const Icode 
	cosh(const Icode& x)
	{
		return gaol::cosh(x);
	}
	static const Icode 
	tanh(const Icode& x)
	{
		return gaol::tanh(x);
	}
	static const Icode 
	asin(const Icode& x)
	{
		return gaol::asin(x);
	}
	static const Icode 
	acos(const Icode& x)
	{
		return gaol::acos(x);
	}
	static const Icode 
	atan(const Icode& x)
	{
		return gaol::atan(x);
	}
	static const Icode 
	asinh(const Icode& x)
	{
		return gaol::asinh(x);
	}
	static const Icode 
	acosh(const Icode& x)
	{
		return gaol::acosh(x);
	}
	static const Icode 
	atanh(const Icode& x)
	{
		return gaol::atanh(x);
	}

	static const Icode 
	asin_rel(const Icode& lhs, const Icode& rhs)
	{
		return gaol::asin_rel(lhs, rhs);
	}
	static const Icode 
	acos_rel(const Icode& lhs, const Icode& rhs)
	{
		return gaol::acos_rel(lhs, rhs);
	}
	static const Icode 
	atan_rel(const Icode& lhs, const Icode& rhs)
	{
		return gaol::atan_rel(lhs, rhs);
	}
	static const Icode 
	asinh_rel(const Icode& lhs, const Icode& rhs)
	{
		return gaol::asinh_rel(lhs, rhs);
	}
	static const Icode 
	acosh_rel(const Icode& lhs, const Icode& rhs)
	{
		return gaol::acosh_rel(lhs, rhs);
	}
	static const Icode 
	atanh_rel(const Icode& lhs, const Icode& rhs)
	{
		return gaol::atanh_rel(lhs, rhs);
	}
	static const Icode 
	sqrt_rel(const Icode& lhs, const Icode& rhs)
	{
		return gaol::sqrt_rel(lhs, rhs);
	}
	static const Icode 
	root_rel(const Icode& lhs, int n, const Icode& rhs)
	{
		return gaol::nth_root_rel(lhs, n, rhs);
	}
	static const Icode 
	invabs_rel(const Icode& lhs, const Icode& rhs)
	{
		return gaol::invabs_rel(lhs, rhs);
	}
	static const Icode 
	div_rel(const Icode& k, const Icode& lhs, const Icode& rhs)
	{
		return gaol::div_rel(k, lhs, rhs);
	}
	//@}


	/**
	 * @name Non-arithmetic operations
	 */
	//@{
	/// @name Interval-valued operations on non-interval args
	//@{
	// TODO: hull, sumAll, innerProd
	//@}

	/// @name Numeral-valued unary operations on an interval
	//@{
	/// Inferior bound
	static const Bound
	inf(const Icode& x)
	{
		return x.left();
	}
	/// Superior bound
	static const Bound
	sup(const Icode& x)
	{
		return x.right();
	}

	/// Approximation of the midpoint.
	static const Bound
	mid(const Icode& x)
	{
		return x.midpoint();
	}

	/// Approximation of the integer midpoint.
	static const Bound
	midInt(const Icode& x)
	{
		return std::ceil(x.midpoint());
	}

	/// Return the width of [l,u] = (u-l) upward rounded.
	static const Bound
	width(const Icode& x)
	{
		return x.width();
	}

	/// Magnitude (maximum of absolute values).
	static const Bound
	mag(const Icode& x)
	{
		return x.mag();
	}

	/// Mignitude (minimum of absolute values).
	static const Bound
	mig(const Icode& x)
	{
		return x.mig();
	}
	//@}

	/// @name Interval-valued unary operations on an interval
	//@{
	/// Return [ceil(l), floor(u)].
	static const Icode
	toInt(const Icode& x)
	{
		return gaol::integer(x);
	}

	// TODO: inf, sup, iinf, isup
	//@}

	/// @name Boolean-valued binary operations on an interval
	//@{
	/// If x is a point interval [a,a].
	static bool
	isPoint(const Icode& x)
	{
		return x.is_a_double();
	}

	/// If x is equal to the interval [0,0].
	static bool
	isZero(const Icode& x)
	{
		return x.is_zero();
	}

	/// If x contains at most two machine numbers.
	static bool
	isCanonical(const Icode& x)
	{
		return x.is_canonical();
	}

	/// If x is an integer interval.
	static bool
	isInt(const Icode& x)
	{
		return x.is_an_int();
	}

	/// If x contains at most n machine numbers.
	static bool
	isPrecise(const Icode& x, int n)
	{
		return (gaol::nb_fp_numbers(x.left(), x.right()) <= n);
	}

	/// If x is empty.
	static bool
	isEmpty(const Icode& x)
	{
		return x.is_empty();
	}
	//@}

	/// @name Boolean-valued binary operations on intervals
	//@{
	static bool
	equalTo(const Icode& lhs, const Icode& rhs)
	{
		return lhs.set_eq(rhs);
	}
	static bool
	unequalTo(const Icode& lhs, const Icode& rhs)
	{
		return !lhs.set_eq(rhs);
	}
	static bool
	possiblyEqualTo(const Icode& lhs, const Icode& rhs)
	{
		return lhs.possibly_eq(rhs);
	}
	static bool
	certainlyEqualTo(const Icode& lhs, const Icode& rhs)
	{
		return lhs.certainly_eq(rhs);
	}

	static bool
	lessThan(const Icode& lhs, const Icode& rhs)
	{
		return lhs.set_le(rhs);
	}
	static bool
	possiblyLessThan(const Icode& lhs, const Icode& rhs)
	{
		return lhs.possibly_leq(rhs);
	}
	static bool
	certainlyLessThan(const Icode& lhs, const Icode& rhs)
	{
		return lhs.certainly_le(rhs);
	}

	// TODO: omega, containedIn
	//@}

	/// @name Numeral-valued binary operations on intervals
	//@{
	// TODO: signed distance, hausdorff distance, projection
	//@}

	/// @name Interval-valued binary operations on intervals
	//@{
	static const Icode
	intersection(const Icode& lhs, const Icode& rhs)
	{
		return gaol::operator&(lhs, rhs);
	}

	static const Icode
	convexHull(const Icode& lhs, const Icode& rhs)
	{
		if (lhs.is_empty()) {
			return rhs;
		} else {
			if (rhs.is_empty()) {
				return lhs;
			} else {
				return Icode(std::min(lhs.left(), rhs.left()), std::max(lhs.right(), rhs.right()));
			}
		}
	}

	static const Icode
	innerAddition(const Icode& lhs, const Icode& rhs)
	{
		double l(lhs.left() + rhs.right());
		double u(lhs.right() + rhs.left());
		if (l <= u)
			return Icode(l, u);
		else
			return Icode::emptyset;
	}

	static const Icode
	innerSubtraction(const Icode& lhs, const Icode& rhs)
	{
		if ((lhs.left() + rhs.right()) <= (lhs.right() + rhs.left()))
			return Icode((lhs.left() - rhs.left()), (lhs.right() - rhs.right()));
		else
			return Icode::emptyset;
	}
	//@}

	/// @brief Division with gap
	static const Icode
	divisionWithGap(const Icode& lhs, const Icode& rhs)
	{
		throw("unsupported");
	}

	/// @brief Division
	static const std::pair<Icode, Icode>
	division(const Icode& x)
	{
		Icode l, u;
		x.split(l, u);
		return std::pair<Icode, Icode>(l, u);
	}
	//@}

	/**
 	 * @name printing methods
 	 */
	//@{
	/// Output of *this on os
	static void print(std::ostream& os, const Icode& x)
	{
		if (x.left() == x.right()) os << x.left();
		else gaol::operator<<(os, x);
	}

	static void setOutputDigits(int n)
	{
		Icode::precision(n);
	}
	//@}
};

/**
 * @brief Traits class for interval bounds.
 */
template <>
struct BoundTraits<double>
{
	/// @name Bound operations.
	//@{
	/// Machine number following r.
	static const Bound
	nextUp(const Bound& x)
	{
		return gaol::next_float(x);
	}

	/// Machine number before r.
	static const Bound
	nextDown(const Bound& x)
	{
		return gaol::previous_float(x);
	}

	/// Conversion from Bound to double.
	static double
	toDouble(const Bound& x)
	{
		return x;
	}

	/// Conversion from Bound to integer.
	static int
	toInt(const Bound& x)
	{
		return std::floor(x);
	}
	//@}

};

END_NAMESPACE_ELISA

#endif // ELISA_INTERVAL_GAOL_H
